package com.example.demo.DynamicDataSource;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * 重写动态数据源实现
 */
public class  DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware {

	private ApplicationContext applicationContext ;

	/**
	 * 连接数据源前,调用该方法
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		//1.获取手动设置的数据源参数DataSourceBean
		DataSourceBean dataSourceBean = DataSourceContext.getDataSource();
		if(dataSourceBean == null) {
			return null;
		}
		try {
			//2.获取AbstractRoutingDataSource的targetDataSources属性,该属性存放数据源属性
			Map<Object, Object> targetSourceMap = getTargetSource();
			synchronized(this) {
				//如果每次需要重新载入
				if(dataSourceBean.getReloadEveryTime()){
					if(!dataSourceBean.getBeanName().equals("defaultDataSource")){
						Object dataSource = createDataSource(dataSourceBean,targetSourceMap);

						targetSourceMap.put(dataSourceBean.getBeanName(), dataSource);
						super.afterPropertiesSet();
					}
				}else{//不需要重新载入
					if(!targetSourceMap.keySet().contains(dataSourceBean.getBeanName())){
						Object dataSource = createDataSource(dataSourceBean,null);
						targetSourceMap.put(dataSourceBean.getBeanName(), dataSource);
						super.afterPropertiesSet();
					}
				}


				/*
				 * 3.判断targetDataSources中是否已经存在要设置的数据源bean
				 * 存在的话,则直接返回beanName
				 *
				 */
				//if(!targetSourceMap.keySet().contains(dataSourceBean.getBeanName())&&!targetSourceMap.get(dataSourceBean.getBeanName()).equals("defaultDataSource")) {
					/*不存在，则进行以下几步
					3.1 先在spring容器中创建该数据源bean
					*/
					//----Object dataSource = createDataSource(dataSourceBean);
					//3.2 在创建后的bean,放入到targetDataSources Map中
					//----targetSourceMap.put(dataSourceBean.getBeanName(), dataSource);
					/*
					 * 3.3 通知spring有bean更新
					 * 主要更新AbstractRoutingDataSource的resolvedDefaultDataSource(Map)属性,
					 * 更新完以后,AbstractRoutingDataSource的determineTargetDataSource()中,才能找到数据源
					 * 代码如下：
					 * Object lookupKey = determineCurrentLookupKey();
					   DataSource dataSource = this.resolvedDataSources.get(lookupKey);
					 */
					//----super.afterPropertiesSet();
				//}

				/**
				 * new Logic 如果当前数据源不是默认的数据源那么都需要重新新增，这是为了解决数据库配置失败后，无法重新再重新使用
				 * 缺点是每次都会去重新注入新的bean影响性能
				 */
				/*if(!dataSourceBean.getBeanName().equals("defaultDataSource")){
					Object dataSource = createDataSource(dataSourceBean,targetSourceMap);

					targetSourceMap.put(dataSourceBean.getBeanName(), dataSource);
					super.afterPropertiesSet();
				}*/


			}
			return dataSourceBean.getBeanName();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 根据数据源信息在spring中创建bean,并返回
	 * @param dataSourceBean 数据源信息
	 * @return
	 * @throws IllegalAccessException
	 */
	public Object createDataSource(DataSourceBean dataSourceBean,Map<Object, Object> targetSourceMap) throws IllegalAccessException {
		Object obj= null;
		try {
			//1.将applicationContext转化为ConfigurableApplicationContext
			ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
			//2.获取bean工厂并转换为DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory =  (DefaultListableBeanFactory) context.getBeanFactory();
			/*
			 * 3.本文用的是DruidDataSource,所有在这里我们获取的是该bean的BeanDefinitionBuilder,
			 * 通过BeanDefinitionBuilder来创建bean定义
			 */
			//如果之前已经创建那么先删除
			if(targetSourceMap!=null&&targetSourceMap.containsKey(dataSourceBean.getBeanName())){
				beanFactory.removeBeanDefinition(dataSourceBean.getBeanName());
				targetSourceMap.remove(dataSourceBean.getBeanName());
			}

			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
			/**
			 * 4.获取DataSourceBean里的属性和对应值,并将其交给BeanDefinitionBuilder创建bean的定义
			 */
			Map<String, Object> propertyKeyValues = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);
			for(Map.Entry<String,Object> entry : propertyKeyValues.entrySet()) {
				beanDefinitionBuilder.addPropertyValue(entry.getKey(), entry.getValue());
			}
			//5.bean定义好以后,将其交给beanFactory注册成bean对象，由spring容器管理
			beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());
			//6.最后获取步骤5生成的bean,并将其返回
			obj = context.getBean(dataSourceBean.getBeanName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return obj;
	}
	//获取类属性和对应的值,放入Map中
    @SuppressWarnings("unused")
	private <T> Map<String, Object> getPropertyKeyValues(Class<T> clazz, Object object) throws IllegalAccessException {
       Field[] fields = clazz.getDeclaredFields();
       Map<String,Object> map = new HashMap<>();
       for (Field field : fields) {
    	   field.setAccessible(true);
    	   map.put(field.getName(), field.get(object));
       }
       map.remove("beanName");
       map.remove("reloadEveryTime");//需要去除的属性
       return map;
    }
    //通过反射获取AbstractRoutingDataSource的targetDataSources属性
	@SuppressWarnings("unchecked")
	public Map<Object, Object> getTargetSource() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		Field field = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
		field.setAccessible(true);
		return (Map<Object, Object>) field.get(this);
	}
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
			this.applicationContext = applicationContext;
	}
}
